package examples.jsf.component;

import examples.jsf.AbortProcessingException;
import examples.jsf.context.FacesContext;
import examples.jsf.event.FacesEvent;
import examples.jsf.event.PhaseId;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Bill
 * @version 2013-05-29
 */
public class UIViewRoot extends UIComponentBase {

    private boolean skipPhase = false;


    /**
     * <div class="changed_added_2_0">
     * <p>Perform partial processing by calling
     * {@link javax.faces.context.PartialViewContext#processPartial} with
     * {@link PhaseId#APPLY_REQUEST_VALUES} if:
     * <ul>
     * <li>{@link javax.faces.context.PartialViewContext#isPartialRequest}
     * returns <code>true</code> and we don't have a request to process all
     * components in the view
     * ({@link javax.faces.context.PartialViewContext#isExecuteAll} returns
     * <code>false</code>)</li>
     * </ul>
     * Perform full processing by calling
     * {@link UIComponentBase#processDecodes} if one of the following
     * conditions are met:
     * <ul>
     * <li> {@link javax.faces.context.PartialViewContext#isPartialRequest}
     * returns <code>true</code> and we have a request to process all
     * components in the view
     * ({@link javax.faces.context.PartialViewContext#isExecuteAll} returns
     * <code>true</code>)</li>
     * <li>{@link javax.faces.context.PartialViewContext#isPartialRequest}
     * returns <code>false</code></li>
     * </ul>
     * </p>
     * </div>
     * <p class="changed_modified_2_0">Override the default
     * {@link UIComponentBase#processDecodes} behavior to broadcast any queued
     * events after the default processing or partial processing has been
     * completed and to clear out any events for later phases if the event
     * processing for this phase caused {@link FacesContext#renderResponse}
     * or {@link FacesContext#responseComplete} to be called.</p>
     *
     * @param context {@link FacesContext} for the request we are processing
     *
     * @throws NullPointerException if <code>context</code>
     *                              is <code>null</code>
     */
    @Override
    public void processDecodes(FacesContext context) {
        initState();
        notifyBefore(context, PhaseId.APPLY_REQUEST_VALUES);

        try {
            if (!skipPhase) {
                if (context.getPartialViewContext().isPartialRequest() &&
                        !context.getPartialViewContext().isExecuteAll()) {
                    context.getPartialViewContext().processPartial(PhaseId.APPLY_REQUEST_VALUES);
                } else {
                    super.processDecodes(context);
                }
                broadcastEvents(context, PhaseId.APPLY_REQUEST_VALUES);
            }
        } finally {
            clearFacesEvents(context);
            notifyAfter(context, PhaseId.APPLY_REQUEST_VALUES);
        }
    }

    /**
     * <div class="changed_added_2_0">
     * <p>Perform partial processing by calling
     * {@link javax.faces.context.PartialViewContext#processPartial} with
     * {@link PhaseId#PROCESS_VALIDATIONS} if:
     * <ul>
     * <li>{@link javax.faces.context.PartialViewContext#isPartialRequest}
     * returns <code>true</code> and we don't have a request to process all
     * components in the view
     * ({@link javax.faces.context.PartialViewContext#isExecuteAll} returns
     * <code>false</code>)</li>
     * </ul>
     * Perform full processing by calling
     * {@link UIComponentBase#processValidators} if one of the following
     * conditions are met:
     * <ul>
     * <li> {@link javax.faces.context.PartialViewContext#isPartialRequest}
     * returns <code>true</code> and we have a request to process all
     * components in the view
     * ({@link javax.faces.context.PartialViewContext#isExecuteAll} returns
     * <code>true</code>)</li>
     * <li>{@link javax.faces.context.PartialViewContext#isPartialRequest}
     * returns <code>false</code></li>
     * </ul>
     * </p>
     * </div>
     * <p class="changed_modified_2_0">Override the default
     * {@link UIComponentBase#processValidators} behavior to broadcast any
     * queued events after the default processing or partial processing has been
     * completed and to clear out any events for later phases if the event
     * processing for this phase caused {@link FacesContext#renderResponse} or
     * {@link FacesContext#responseComplete} to be called.</p>
     *
     * @param context {@link FacesContext} for the request we are processing
     *
     * @throws NullPointerException if <code>context</code>
     *                              is <code>null</code>
     */
    @Override
    public void processValidators(FacesContext context) {
        initState();
        notifyBefore(context, PhaseId.PROCESS_VALIDATIONS);

        try {
            if (!skipPhase) {
                if (context.getPartialViewContext().isPartialRequest() &&
                        !context.getPartialViewContext().isExecuteAll()) {
                    context.getPartialViewContext().processPartial(PhaseId.PROCESS_VALIDATIONS);
                } else {
                    super.processValidators(context);
                }
                broadcastEvents(context, PhaseId.PROCESS_VALIDATIONS);
            }
        } finally {
            clearFacesEvents(context);
            notifyAfter(context, PhaseId.PROCESS_VALIDATIONS);
        }
    }

    /**
     * <div class="changed_added_2_0">
     * <p>Perform partial processing by calling
     * {@link javax.faces.context.PartialViewContext#processPartial} with
     * {@link PhaseId#UPDATE_MODEL_VALUES} if:
     * <ul>
     * <li>{@link javax.faces.context.PartialViewContext#isPartialRequest}
     * returns <code>true</code> and we don't have a request to process all
     * components in the view
     * ({@link javax.faces.context.PartialViewContext#isExecuteAll} returns
     * <code>false</code>)</li>
     * </ul>
     * Perform full processing by calling
     * {@link UIComponentBase#processUpdates} if one of the following
     * conditions are met:
     * <ul>
     * <li> {@link javax.faces.context.PartialViewContext#isPartialRequest}
     * returns <code>true</code> and we have a request to process all
     * components in the view
     * ({@link javax.faces.context.PartialViewContext#isExecuteAll} returns
     * <code>true</code>)</li>
     * <li>{@link javax.faces.context.PartialViewContext#isPartialRequest}
     * returns <code>false</code></li>
     * </ul>
     * </p>
     *</div>
     * <p class="changed_modified_2_0">Override the default {@link UIComponentBase}
     * behavior to broadcast any queued events after the default processing or
     * partial processing has been completed and to clear out any events for
     * later phases if the event processing for this phase caused
     * {@link FacesContext#renderResponse} or
     * {@link FacesContext#responseComplete} to be called.</p>
     *
     * @param context {@link FacesContext} for the request we are processing
     *
     * @throws NullPointerException if <code>context</code>
     *                              is <code>null</code>
     */
    @Override
    public void processUpdates(FacesContext context) {
        initState();
        notifyBefore(context, PhaseId.UPDATE_MODEL_VALUES);

        try {
            if (!skipPhase) {
                if (context.getPartialViewContext().isPartialRequest() &&
                        !context.getPartialViewContext().isExecuteAll()) {
                    context.getPartialViewContext().processPartial(PhaseId.UPDATE_MODEL_VALUES);
                } else {
                    super.processUpdates(context);
                }
                broadcastEvents(context, PhaseId.UPDATE_MODEL_VALUES);
            }
        } finally {
            clearFacesEvents(context);
            notifyAfter(context, PhaseId.UPDATE_MODEL_VALUES);
        }
    }


    /**
     * <p>Broadcast any events that have been queued for the <em>Invoke
     * Application</em> phase of the request processing lifecycle
     * and to clear out any events for later phases if the event processing
     * for this phase caused {@link FacesContext#renderResponse} or
     * {@link FacesContext#responseComplete} to be called.</p>
     *
     * @param context {@link FacesContext} for the request we are processing
     *
     * @throws NullPointerException if <code>context</code>
     *                              is <code>null</code>
     */
    public void processApplication(FacesContext context) {
        initState();
        notifyBefore(context, PhaseId.INVOKE_APPLICATION);
        try {
            if (!skipPhase) {
                // NOTE - no tree walk is performed; this is a UIViewRoot-only operation
                broadcastEvents(context, PhaseId.INVOKE_APPLICATION);
            }
        } finally {
            clearFacesEvents(context);
            notifyAfter(context, PhaseId.INVOKE_APPLICATION);
        }
    }

    private void notifyAfter(FacesContext context, PhaseId invokeApplication) {
        //To change body of created methods use File | Settings | File Templates.
    }

    private void notifyBefore(FacesContext context, PhaseId invokeApplication) {
        //To change body of created methods use File | Settings | File Templates.
    }

    private void clearFacesEvents(FacesContext context) {
        //To change body of created methods use File | Settings | File Templates.
    }

    private List<List<FacesEvent>> events = null;

    // 普通组件queueEvent是向父组件委托，但是这里不同，本组件是ViewRoot, 是整棵组件树的root,
    // 无处可以委托，必须自己实现queueEvent
    @Override
    public void queueEvent(FacesEvent event) {
        if (event == null) {
            throw new NullPointerException();
        }
        // We are a UIViewRoot, so no need to check for the ISE
        if (events == null) {
            int len = PhaseId.VALUES.size();
            List<List<FacesEvent>> events = new ArrayList<List<FacesEvent>>(len);
            for (int i = 0; i < len; i++) {
                events.add(new ArrayList<FacesEvent>(5));
            }
            this.events = events;
        }
        events.get(event.getPhaseId().getOrdinal()).add(event);
    }

    /**
     * <p class="changed_added_2_0">Broadcast any events that have been
     * queued.  First broadcast events that have been queued for {@link
     * PhaseId#ANY_PHASE}.  Then broadcast ane events that have been
     * queued for the current phase.  In both cases, {@link
     * UIComponent#pushComponentToEL} must be called before the event is
     * broadcast, and {@link UIComponent#popComponentFromEL} must be
     * called after the return from the broadcast, even in the case of
     * an exception.</p>
     *
     * @param context {@link FacesContext} for the current request
     * @param phaseId {@link PhaseId} of the current phase

     * @since 2.0
     */
    public void broadcastEvents(FacesContext context, PhaseId phaseId) {

        if (null == events) {
            // no events have been queued
            return;
        }

        List<FacesEvent> eventsForPhaseId =  events.get(PhaseId.ANY_PHASE.getOrdinal());

        // 先处理 Any_Phase event
        while (! eventsForPhaseId.isEmpty()) {

            FacesEvent event = eventsForPhaseId.get(0);
            UIComponent source = event.getComponent();
            UIComponent compositeParent = null;

            try {
                if (!UIComponent.isCompositeComponent(source)) {
                    compositeParent = UIComponent.getCompositeComponentParent(source);
                }
                if (compositeParent != null) {
                    compositeParent.pushComponentToEL(context, null);
                }
                source.pushComponentToEL(context, null);
                source.broadcast(event);
            } catch (AbortProcessingException e) {
                // application queue exception event
            }
            finally {
                source.popComponentFromEL(context);
                if (compositeParent != null) {
                    compositeParent.popComponentFromEL(context);
                }
            }
            eventsForPhaseId.remove(0); // Stay at current position

        }

        // 在处理当前Phase的事件
        if (null != (eventsForPhaseId = events.get(phaseId.getOrdinal()))) {
            while (! eventsForPhaseId.isEmpty()) {
                // 过程省略
            }
        }

    }

    private void initState() {
//        skipPhase = false;
//        beforeMethodException = false;
//        //noinspection unchecked
//        List<PhaseListener> listeners =
//                (List<PhaseListener>) getStateHelper().get(PropertyKeys.phaseListeners);
//        phaseListenerIterator =
//                ((listeners != null) ? listeners.listIterator() : null);
    }

}
